<template>
  <div :style="style" :class="containerKls">
    <!-- icon & line -->
    <div :class="[ns.e('head'), ns.is(currentStatus)]">
      <div v-if="!isSimple" :class="ns.e('line')">
        <i :class="ns.e('line-inner')" :style="lineStyle" />
      </div>

      <div
        :class="[ns.e('icon'), ns.is(icon || $slots.icon ? 'icon' : 'text')]"
      >
        <slot name="icon">
          <el-icon v-if="icon" :class="ns.e('icon-inner')">
            <component :is="icon" />
          </el-icon>
          <el-icon
            v-else-if="currentStatus === 'success'"
            :class="[ns.e('icon-inner'), ns.is('status')]"
          >
            <Check />
          </el-icon>
          <el-icon
            v-else-if="currentStatus === 'error'"
            :class="[ns.e('icon-inner'), ns.is('status')]"
          >
            <Close />
          </el-icon>
          <div v-else-if="!isSimple" :class="ns.e('icon-inner')">
            {{ index + 1 }}
          </div>
        </slot>
      </div>
    </div>
    <!-- title & description -->
    <div :class="ns.e('main')">
      <div :class="[ns.e('title'), ns.is(currentStatus)]">
        <slot name="title">{{ title }}</slot>
      </div>
      <div v-if="isSimple" :class="ns.e('arrow')" />
      <div v-else :class="[ns.e('description'), ns.is(currentStatus)]">
        <slot name="description">{{ description }}</slot>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import {
  computed,
  getCurrentInstance,
  inject,
  onBeforeUnmount,
  onMounted,
  ref,
  watch,
} from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { ElIcon } from '@element-plus/components/icon'
import { Check, Close } from '@element-plus/icons-vue'
import { isNumber } from '@element-plus/utils'
import { stepProps } from './item'
import { STEPS_INJECTION_KEY } from './tokens'

import type { CSSProperties, ComputedRef, Ref, VNode } from 'vue'
import type { StepsProps } from './steps'

export interface StepItemState {
  uid: number
  getVnode: () => VNode
  currentStatus: ComputedRef<string>
  internalStatus: Ref<string>
  setIndex: (val: number) => void
  calcProgress: (status: string) => void
}

export interface IStepsInject {
  props: StepsProps
  steps: Ref<StepItemState[]>
  addStep: (item: StepItemState) => void
  removeStep: (item: StepItemState) => void
}

defineOptions({
  name: 'ElStep',
})

const props = defineProps(stepProps)
const ns = useNamespace('step')
const index = ref(-1)
const lineStyle = ref({})
const internalStatus = ref('')
const parent = inject(STEPS_INJECTION_KEY) as IStepsInject
const currentInstance = getCurrentInstance()!
let stepDiff = 0
let beforeActive = 0

onMounted(() => {
  watch(
    [
      () => parent.props.active,
      () => parent.props.processStatus,
      () => parent.props.finishStatus,
    ],
    ([active], [oldActive]) => {
      beforeActive = oldActive || 0
      stepDiff = active - beforeActive

      updateStatus(active)
    },
    { immediate: true }
  )
})

const currentStatus = computed(() => {
  return props.status || internalStatus.value
})

const prevInternalStatus = computed(() => {
  const prevStep = parent.steps.value[index.value - 1]
  return prevStep ? prevStep.internalStatus.value : 'wait'
})

const isCenter = computed(() => {
  return parent.props.alignCenter
})

const isVertical = computed(() => {
  return parent.props.direction === 'vertical'
})

const isSimple = computed(() => {
  return parent.props.simple
})

const stepsCount = computed(() => {
  return parent.steps.value.length
})

const isLast = computed(() => {
  return parent.steps.value[stepsCount.value - 1]?.uid === currentInstance.uid
})

const space = computed(() => {
  return isSimple.value ? '' : parent.props.space
})

const containerKls = computed(() => {
  return [
    ns.b(),
    ns.is(isSimple.value ? 'simple' : parent.props.direction),
    ns.is('flex', isLast.value && !space.value && !isCenter.value),
    ns.is('center', isCenter.value && !isVertical.value && !isSimple.value),
  ]
})

const style = computed(() => {
  const style: CSSProperties = {
    flexBasis: isNumber(space.value)
      ? `${space.value}px`
      : space.value
        ? space.value
        : `${100 / (stepsCount.value - (isCenter.value ? 0 : 1))}%`,
  }
  if (isVertical.value) return style
  if (isLast.value) {
    style.maxWidth = `${100 / stepsCount.value}%`
  }
  return style
})

const setIndex = (val: number) => {
  index.value = val
}

const calcProgress = (status: string) => {
  const isWait = status === 'wait'
  const delayTimer =
    Math.abs(stepDiff) === 1
      ? 0
      : stepDiff > 0
        ? (index.value + 1 - beforeActive) * 150
        : -(index.value + 1 - parent.props.active) * 150

  const style: CSSProperties = {
    transitionDelay: `${delayTimer}ms`,
  }
  const step = status === parent.props.processStatus || isWait ? 0 : 100

  style.borderWidth = step && !isSimple.value ? '1px' : 0
  style[parent.props.direction === 'vertical' ? 'height' : 'width'] = `${step}%`
  lineStyle.value = style
}

const updateStatus = (activeIndex: number) => {
  if (activeIndex > index.value) {
    internalStatus.value = parent.props.finishStatus
  } else if (
    activeIndex === index.value &&
    prevInternalStatus.value !== 'error'
  ) {
    internalStatus.value = parent.props.processStatus
  } else {
    internalStatus.value = 'wait'
  }
  const prevChild = parent.steps.value[index.value - 1]
  if (prevChild) prevChild.calcProgress(internalStatus.value)
}

const stepItemState: StepItemState = {
  uid: currentInstance.uid,
  getVnode: () => currentInstance.vnode,
  currentStatus,
  internalStatus,
  setIndex,
  calcProgress,
}

parent.addStep(stepItemState)

onBeforeUnmount(() => {
  parent.removeStep(stepItemState)
})
</script>
